package org.framework.factory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.framework.annotation.properties.ArgumentSetting;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @ClassName MessageQueueFactoryBean
 * @Description
 * @Author RayLiang
 * @Date 2022/12/26 16:05
 * @Version 1.0
 **/
public class MessageQueueFactoryBean implements FactoryBean<Queue> {


    private final Log logger = LogFactory.getLog(this.getClass());

    private String queueName;
    private boolean durable;
    private boolean exclusive;
    private boolean autoDelete;

    private ArgumentSetting[] arguments;
    private BeanFactory beanFactory;
    private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
    private BeanExpressionContext expressionContext;
    private ClassLoader beanClassLoader;
    private int order;


    private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();

    private final Set<String> emptyStringArguments = new HashSet<>();

    public MessageQueueFactoryBean(String queueName, boolean durable, boolean exclusive, boolean autoDelete, ArgumentSetting[] arguments, BeanFactory beanFactory, BeanExpressionResolver resolver, BeanExpressionContext expressionContext, ClassLoader beanClassLoader,int order) {
        this.queueName = queueName;
        this.durable = durable;
        this.exclusive = exclusive;
        this.autoDelete = autoDelete;
        this.arguments = arguments;
        this.beanFactory = beanFactory;
        this.resolver = resolver;
        this.expressionContext = expressionContext;
        this.beanClassLoader = beanClassLoader;
        this.order = order;
    }

    @Override
    public Queue getObject() throws Exception {
        return new Queue(this.queueName, this.durable, this.exclusive, this.autoDelete, resolveArguments(this.arguments));
    }

    @Override
    public Class<?> getObjectType() {
        return Queue.class;
    }


    private Map<String, Object> resolveArguments(ArgumentSetting[] arguments) {
        Map<String, Object> map = new HashMap<String, Object>(8);
        for (ArgumentSetting arg : arguments) {
            if(Objects.isNull(arg)){
                continue;
            }
            String key = resolveExpressionAsString(arg.getName(), "@Argument.name");
            if (StringUtils.hasText(key)) {
                Object value = resolveExpression(arg.getValue());
                Object type = resolveExpression(arg.getType());
                Class<?> typeClass;
                String typeName;
                if (type instanceof Class) {
                    typeClass = (Class<?>) type;
                    typeName = typeClass.getName();
                } else {
                    Assert.isTrue(type instanceof String, () -> "Type must resolve to a Class or String, but resolved to [" + type.getClass().getName() + "]");
                    typeName = (String) type;
                    try {
                        typeClass = ClassUtils.forName(typeName, this.beanClassLoader);
                    } catch (Exception e) {
                        throw new IllegalStateException("Could not load class", e);
                    }
                }
                addToMap(map, key, value, typeClass, typeName);
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("@Argument ignored because the name resolved to an empty String");
                }
            }
        }
        return map.size() < 1 ? null : map;
    }

    private void addToMap(Map<String, Object> map, String key, Object value, Class<?> typeClass, String typeName) {
        if (value.getClass().getName().equals(typeName)) {
            if (typeClass.equals(String.class) && !StringUtils.hasText((String) value)) {
                putEmpty(map, key);
            } else {
                map.put(key, value);
            }
        } else {
            if (value instanceof String && !StringUtils.hasText((String) value)) {
                putEmpty(map, key);
            } else {
                if (CONVERSION_SERVICE.canConvert(value.getClass(), typeClass)) {
                    map.put(key, CONVERSION_SERVICE.convert(value, typeClass));
                } else {
                    throw new IllegalStateException("Cannot convert from " + value.getClass().getName() + " to " + typeName);
                }
            }
        }
    }

    private String resolveExpressionAsString(String value, String attribute) {
        Object resolved = resolveExpression(value);
        if (resolved instanceof String) {
            return (String) resolved;
        } else {
            throw new IllegalStateException("The [" + attribute + "] must resolve to a String. " + "Resolved to [" + resolved.getClass() + "] for [" + value + "]");
        }
    }

    private void putEmpty(Map<String, Object> map, String key) {
        if (this.emptyStringArguments.contains(key)) {
            map.put(key, "");
        } else {
            map.put(key, null);
        }
    }

    private Object resolveExpression(String value) {
        String resolvedValue = resolve(value);

        return this.resolver.evaluate(resolvedValue, this.expressionContext);
    }


    private String resolve(String value) {
        if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) {
            return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
        }
        return value;
    }
}
